/**
******************************************************************************
*
* @file       mapgraphicitem.cpp
* @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
* @brief      The main graphicsItem used on the widget, contains the map and map logic
* @see        The GNU Public License (GPL) Version 3
* @defgroup   OPMapWidget
* @{
*
*****************************************************************************/

/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include "uavitem.h"
#include "gpsitem.h"
#include "homeitem.h"
#include "mapgraphicitem.h"
#include "waypointlineitem.h"
#include <QGraphicsSceneMouseEvent>

namespace mapcontrol {

MapGraphicItem::MapGraphicItem(internals::Core *core, Configuration *configuration) :
    core(core),
    config(configuration),
    MapRenderTransform(1),
    maxZoom(17),
    minZoom(2),
    zoomReal(0),
    rotation(0),
    zoomDigi(0),
    isSelected(false)
{
    dragons.load(QString::fromUtf8(":/markers/images/dragons1.jpg"));
    showTileGridLines=false;
    isMouseOverMarker=false;
    maprect=QRectF(0,0,1022,680);
    core->SetCurrentRegion(internals::Rectangle(0, 0, maprect.width(), maprect.height()));
    core->SetMapType(MapType::GoogleHybrid);
    this->SetZoom(2);
    connect(core,SIGNAL(OnNeedInvalidation()),this,SLOT(Core_OnNeedInvalidation()));
    connect(core,SIGNAL(OnMapDrag()),this,SLOT(ChildPosRefresh()));
    connect(core,SIGNAL(OnMapZoomChanged()),this,SLOT(ChildPosRefresh()));
    //resize();
}

void MapGraphicItem::start()
{
    core->StartSystem();
}

void MapGraphicItem::resize(const QRectF &rect)
{
    Q_UNUSED(rect);
    {
        this->prepareGeometryChange();
        maprect=boundingBox(scene()->sceneRect(),rotation);
        this->setTransform(QTransform().translate(-(maprect.width()-scene()->width())/2,-(maprect.height()-scene()->height())/2));
        this->setTransformOriginPoint(maprect.center().x(),maprect.center().y());
        this->setRotation(rotation);
    }

    core->OnMapSizeChanged(maprect.width(),maprect.height());
    core->SetCurrentRegion(internals::Rectangle(0, 0, maprect.width(), maprect.height()));
    if(isVisible())
    {
        core->GoToCurrentPosition();
    }
}

QRectF MapGraphicItem::boundingRect() const
{
    const int Margin = 1;
    return maprect.adjusted(-Margin, -Margin, +Margin, +Margin);
}

void MapGraphicItem::Core_OnNeedInvalidation()
{
    this->update();
    foreach(QGraphicsItem* i,this->childItems())
    {
        WayPointItem* w=qgraphicsitem_cast<WayPointItem*>(i);
        if(w)
            w->RefreshPos();
        UAVItem* ww=qgraphicsitem_cast<UAVItem*>(i);
        if(ww)
            ww->RefreshPos();
        HomeItem* www=qgraphicsitem_cast<HomeItem*>(i);
        if(www)
            www->RefreshPos();
        GPSItem* wwww=qgraphicsitem_cast<GPSItem*>(i);
        if(wwww)
            wwww->RefreshPos();

        emit mapChanged();
    }
}

void MapGraphicItem::ChildPosRefresh()
{
    foreach(QGraphicsItem* i,this->childItems())
    {
        WayPointItem* w=qgraphicsitem_cast<WayPointItem*>(i);
        if(w)
            w->RefreshPos();
        UAVItem* ww=qgraphicsitem_cast<UAVItem*>(i);
        if(ww)
            ww->RefreshPos();
        HomeItem* www=qgraphicsitem_cast<HomeItem*>(i);
        if(www)
            www->RefreshPos();
        GPSItem* wwww=qgraphicsitem_cast<GPSItem*>(i);
        if(wwww)
            wwww->RefreshPos();

        emit mapChanged();
    }
}

void MapGraphicItem::ConstructLastImage(int const& zoomdiff)
{
    QImage temp;
    QSize size=boundingRect().size().toSize();
    size.setWidth(size.width()*2*zoomdiff);
    size.setHeight(size.height()*2*zoomdiff);
    temp=QImage(size,
                QImage::Format_ARGB32_Premultiplied);
    temp.fill(0);
    QPainter imagePainter(&temp);
    imagePainter.translate(-boundingRect().topLeft());
    imagePainter.scale(2*zoomdiff,2*zoomdiff);
    paintImage(&imagePainter);
    imagePainter.end();
    lastimagepoint=Point(core->GetrenderOffset().X()*2*zoomdiff,core->GetrenderOffset().Y()*2*zoomdiff);
    lastimage=temp;
}

void MapGraphicItem::paintImage(QPainter *painter)
{

    if(MapRenderTransform!=1)
    {
        QTransform transform;
        transform.translate(-((boundingRect().width()*MapRenderTransform)-(boundingRect().width()))/2,-((boundingRect().height()*MapRenderTransform)-(boundingRect().height()))/2);
        transform.scale(MapRenderTransform,MapRenderTransform);
        painter->setWorldTransform(transform);
        {
            DrawMap2D(painter);
        }
        painter->resetTransform();
    }
    else
    {
        DrawMap2D(painter);
    }
    //painter->drawRect(maprect);
}

void MapGraphicItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    if(MapRenderTransform!=1)
    {
        QTransform transform;
        transform.translate(-((boundingRect().width()*MapRenderTransform)-(boundingRect().width()))/2,-((boundingRect().height()*MapRenderTransform)-(boundingRect().height()))/2);
        transform.scale(MapRenderTransform,MapRenderTransform);

        painter->setWorldTransform(transform);
        painter->setRenderHint(QPainter::SmoothPixmapTransform,true);
        painter->setRenderHint(QPainter::HighQualityAntialiasing,true);

        {
            DrawMap2D(painter);
        }
        painter->resetTransform();
    }
    else
    {
        DrawMap2D(painter);
    }
}

void MapGraphicItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(core->IsDragging())
    {
        if(MapRenderTransform!=1)
        {
            qreal dx= (event->pos().x()-core->mouseDown.X())/(MapRenderTransform);
            qreal dy= (event->pos().y()-core->mouseDown.Y())/(MapRenderTransform);
            qreal nx=core->mouseDown.X()+dx;
            qreal ny=core->mouseDown.Y()+dy;
            core->mouseCurrent.SetX(nx);
            core->mouseCurrent.SetY(ny);
        }
        else
        {
            core->mouseCurrent.SetX(event->pos().x());
            core->mouseCurrent.SetY(event->pos().y());
        }
        {
            core->Drag(core->mouseCurrent);
        }

    }
    else if(isSelected && !selectionStart.IsEmpty() && (event->modifiers() == Qt::AltModifier || event->modifiers() == Qt::ShiftModifier))
    {
        selectionEnd = FromLocalToLatLng(event->pos().x(), event->pos().y());
        {
            internals::PointLatLng p1 = selectionStart;
            internals::PointLatLng p2 = selectionEnd;

            double x1 = qMin(p1.Lng(), p2.Lng());
            double y1 = qMax(p1.Lat(), p2.Lat());
            double x2 = qMax(p1.Lng(), p2.Lng());
            double y2 = qMin(p1.Lat(), p2.Lat());

            SetSelectedArea(internals::RectLatLng(y1, x1, x2 - x1, y1 - y2));
        }
    }
    QGraphicsItem::mouseMoveEvent(event);
}

void MapGraphicItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if(!IsMouseOverMarker())
    {
        if(event->button() == config->DragButton && CanDragMap()&& !((event->modifiers()==Qt::AltModifier)||(event->modifiers()==Qt::ShiftModifier)))
        {
            core->mouseDown.SetX(event->pos().x());
            core->mouseDown.SetY(event->pos().y());

            this->setCursor(Qt::SizeAllCursor);

            core->BeginDrag(core->mouseDown);
            this->update();
        }
        else if(!isSelected && ((event->modifiers()==Qt::AltModifier)||(event->modifiers()==Qt::ShiftModifier)))
        {
            isSelected = true;
            SetSelectedArea (internals::RectLatLng::Empty);
            selectionEnd = internals::PointLatLng::Empty;
            selectionStart = FromLocalToLatLng(event->pos().x(), event->pos().y());
        }
    }

}

void MapGraphicItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if(isSelected)
    {
        isSelected = false;
    }

    if(core->IsDragging())
    {
        core->EndDrag();

        this->setCursor(Qt::ArrowCursor);
        if(!BoundsOfMap.IsEmpty() && !BoundsOfMap.Contains(core->CurrentPosition()))
        {
            if(!core->LastLocationInBounds.IsEmpty())
            {
                core->SetCurrentPosition(core->LastLocationInBounds);
            }
        }
    }
    else
    {
        if(!selectionEnd.IsEmpty() && !selectionStart.IsEmpty())
        {
            if(!selectedArea.IsEmpty() && event->modifiers() == Qt::ShiftModifier)
            {
                SetZoomToFitRect(SelectedArea());
            }
        }

    }
}

bool MapGraphicItem::SetZoomToFitRect(internals::RectLatLng const& rect)
{
    int maxZoom = core->GetMaxZoomToFitRect(rect);
    if(maxZoom > 0)
    {
        internals::PointLatLng center=internals::PointLatLng(rect.Lat()-(rect.HeightLat()/2), rect.Lng()+(rect.WidthLng()/2));
        core->SetCurrentPosition(center);

        if(maxZoom > MaxZoom())
        {
            maxZoom = MaxZoom();
        }

        if((int) Zoom() != maxZoom)
        {
            SetZoom(maxZoom);
        }

        return true;
    }
    return false;
}

void MapGraphicItem::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    if(!IsMouseOverMarker() && !IsDragging())
    {
        if(core->GetmouseLastZoom().X() != event->pos().x() && core->mouseLastZoom.Y() != event->pos().y())
        {
            if(GetMouseWheelZoomType() == internals::MouseWheelZoomType::MousePositionAndCenter)
            {
                core->SetCurrentPosition(FromLocalToLatLng(event->pos().x(), event->pos().y()));
            }
            else if(GetMouseWheelZoomType() == internals::MouseWheelZoomType::ViewCenter)
            {
                core->SetCurrentPosition(FromLocalToLatLng((int) maprect.width()/2, (int) maprect.height()/2));
            }
            else if(GetMouseWheelZoomType() == internals::MouseWheelZoomType::MousePositionWithoutCenter)
            {
                core->SetCurrentPosition(FromLocalToLatLng(event->pos().x(), event->pos().y()));

            }

            core->mouseLastZoom.SetX((event->pos().x()));
            core->mouseLastZoom.SetY((event->pos().y()));
        }

        // set mouse position to map center
        if(GetMouseWheelZoomType() != internals::MouseWheelZoomType::MousePositionWithoutCenter)
        {
            {
                //                      System.Drawing.Point p = PointToScreen(new System.Drawing.Point(Width/2, Height/2));
                //                      Stuff.SetCursorPos((int) p.X, (int) p.Y);
            }
        }

        core->MouseWheelZooming = true;

        static const int MapGraphicItemZoomDelta = 8; // This may need to be different for win/linux/mac
        if(event->delta() > MapGraphicItemZoomDelta)
        {
            SetZoom(ZoomTotal()+1);
        }
        else if(event->delta() < -MapGraphicItemZoomDelta)
        {
            SetZoom(ZoomTotal()-1);
        }

        core->MouseWheelZooming = false;
    }
}

void MapGraphicItem::DrawMap2D(QPainter *painter)
{
    painter->setBackground(QBrush(Qt::black));
    if(!lastimage.isNull())
        painter->drawImage(core->GetrenderOffset().X()-lastimagepoint.X(),core->GetrenderOffset().Y()-lastimagepoint.Y(),lastimage);

    for(int i = -core->GetsizeOfMapArea().Width(); i <= core->GetsizeOfMapArea().Width(); i++)
    {
        for(int j = -core->GetsizeOfMapArea().Height(); j <= core->GetsizeOfMapArea().Height(); j++)
        {
            core->SettilePoint (core->GetcenterTileXYLocation());
            core->SettilePoint(Point(core->GettilePoint().X()+ i,core->GettilePoint().Y()+j));
            {
                internals::Tile* t = core->Matrix.TileAt(core->GettilePoint());
                if(true)
                {
                    core->tileRect.SetX(core->GettilePoint().X()*core->tileRect.Width());
                    core->tileRect.SetY(core->GettilePoint().Y()*core->tileRect.Height());
                    core->tileRect.Offset(core->GetrenderOffset());
                    if(core->GetCurrentRegion().IntersectsWith(core->tileRect))
                    {
                        bool found = false;

                        // render tile
                        //lock(t.Overlays)
                        if(t!=0)
                        {
                            foreach(QByteArray img,t->Overlays)
                            {
                                if(img.count()!=0)
                                {
                                    if(!found)
                                        found = true;
                                    {
                                        painter->drawPixmap(core->tileRect.X(),core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height(),PureImageProxy::FromStream(img));
                                        // qDebug()<<"tile:"<<core->tileRect.X()<<core->tileRect.Y();
                                    }
                                }
                            }
                        }

                        if(showTileGridLines)
                        {
                            painter->setPen(config->EmptyTileBorders);
                            painter->drawRect(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height());
                            {
                                painter->setFont(config->MissingDataFont);
                                painter->setPen(Qt::red);
                                painter->drawText(QRectF(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height()),Qt::AlignCenter,(core->GettilePoint() == core->GetcenterTileXYLocation()? "CENTER: " :"TILE: ")+core->GettilePoint().ToString());
                                //qDebug()<<"ShowTileGridLine:"<<core->GettilePoint().ToString()<<"=="<<core->GetcenterTileXYLocation().ToString();
                            }
                        }

                        // add text if tile is missing
                        if(false)
                        {

                            painter->fillRect(QRectF(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height()),config->EmptytileBrush);
                            painter->setFont(config->MissingDataFont);
                            painter->drawText(QRectF(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height()),config->EmptyTileText);



                            painter->setPen(config->EmptyTileBorders);
                            painter->drawRect(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height());

                            // raise error

                        }
                        if(!SelectedArea().IsEmpty())
                        {
                            core::Point p1 = FromLatLngToLocal(SelectedArea().LocationTopLeft());
                            core::Point p2 = FromLatLngToLocal(SelectedArea().LocationRightBottom());
                            int x1 = p1.X();
                            int y1 = p1.Y();
                            int x2 = p2.X();
                            int y2 = p2.Y();
                            painter->setPen(Qt::black);
                            painter->setBrush(QBrush(QColor(50,50,100,20)));
                            painter->drawRect(x1,y1,x2-x1,y2-y1);
                        }
                    }
                }
            }
        }
    }
    // painter->drawRect(core->GetrenderOffset().X()-lastimagepoint.X()-3,core->GetrenderOffset().Y()-lastimagepoint.Y()-3,lastimage.width(),lastimage.height());
    //        painter->setPen(Qt::red);
    //        painter->drawLine(-10,-10,10,10);
    //        painter->drawLine(10,10,-10,-10);
    //        painter->drawRect(boundingRect().adjusted(100,100,-100,-100));
}


core::Point MapGraphicItem::FromLatLngToLocal(internals::PointLatLng const& point)
{
    core::Point ret = core->FromLatLngToLocal(point);
    if(MapRenderTransform!=1)
    {
        ret.SetX((int) (ret.X() * MapRenderTransform));
        ret.SetY((int) (ret.Y() * MapRenderTransform));
        ret.SetX(ret.X()-((boundingRect().width()*MapRenderTransform)-(boundingRect().width()))/2);
        ret.SetY(ret.Y()-((boundingRect().height()*MapRenderTransform)-(boundingRect().height()))/2);


    }
    return ret;
}

internals::PointLatLng MapGraphicItem::FromLocalToLatLng(int x, int y)
{
    if(MapRenderTransform!=1)
    {
        x=x+((boundingRect().width()*MapRenderTransform)-(boundingRect().width()))/2;
        y=y+((boundingRect().height()*MapRenderTransform)-(boundingRect().height()))/2;

        x = (int) (x / MapRenderTransform);
        y = (int) (y / MapRenderTransform);
    }
    return core->FromLocalToLatLng(x, y);
}

float MapGraphicItem::metersToPixels(double meters, internals::PointLatLng coord)
{
    return meters/this->Projection()->GetGroundResolution(this->ZoomTotal(),coord.Lat());
}


double MapGraphicItem::Zoom()
{
    return zoomReal;
}

double MapGraphicItem::ZoomDigi()
{
    return zoomDigi;
}

double MapGraphicItem::ZoomTotal()
{
    return zoomDigi+zoomReal;
}

void MapGraphicItem::SetZoom(double const& value)
{
    if(ZoomTotal() != value)
    {
        if(value > MaxZoom())
        {
            zoomReal = MaxZoom();
            zoomDigi =value-MaxZoom();
        }
        else
            if(value < MinZoom())
            {
                zoomDigi=0;
                zoomReal = MinZoom();
            }
            else
            {
                zoomDigi=0;
                zoomReal = value;
            }
        double integer;
        double remainder = modf (value , &integer);
        if(zoomDigi!=0||remainder != 0)
        {
            float scaleValue = zoomDigi+remainder + 1;
            {
                MapRenderTransform = scaleValue;
                //  qDebug()<<"scale="<<scaleValue<<"zoomdigi:"<<ZoomDigi()<<"integer:"<<integer;
            }
            if(integer>MaxZoom())
                integer=MaxZoom();
            SetZoomStep((qint32)(integer));
            //  core->GoToCurrentPositionOnZoom();
            this->update();

        }
        else
        {

            MapRenderTransform = 1;

            SetZoomStep ((qint32)(value));
            zoomReal = ZoomStep();
            this->update();
        }
    }
}

int MapGraphicItem::ZoomStep()const
{
    return core->Zoom();
}

void MapGraphicItem::SetZoomStep(int const& value)
{
    if(value-core->Zoom()>0 && value<= MaxZoom())
    {
        if (value-core->Zoom() > 2)
        {
            //ConstructLastImage(2);
            lastimage = QImage();
        }
        else
        {
            ConstructLastImage(value-core->Zoom());
        }
    }
    else if(value!=MaxZoom())
        lastimage=QImage();
    if(value > MaxZoom())
    {
        core->SetZoom(MaxZoom());
        emit zoomChanged(MaxZoom()+ZoomDigi(),Zoom(),ZoomDigi());
    }
    else if(value < MinZoom())
    {
        core->SetZoom(MinZoom());
        emit zoomChanged(MinZoom()+ZoomDigi(),Zoom(),ZoomDigi());
    }
    else
    {
        core->SetZoom(value);
        emit zoomChanged(value+ZoomDigi(),Zoom(),ZoomDigi());;
    }

}

void MapGraphicItem::Offset(int const& x, int const& y)
{
    core->DragOffset(Point(x, y));
}

void MapGraphicItem::mapRotate(qreal angle)
{
    if (rotation != angle) {
        rotation=angle;
        resize(scene()->sceneRect());
    }
}

QRectF MapGraphicItem::boundingBox(const QRectF &rect, const qreal &angle)
{
    QRectF ret(rect);
    float c=cos(angle*2*M_PI/360);
    float s=sin(angle*2*M_PI/360);
    ret.setHeight(rect.height()*fabs(c)+rect.width()*fabs(s));
    ret.setWidth(rect.width()*fabs(c)+rect.height()*fabs(s));
    return ret;
}

QSize MapGraphicItem::sizeHint()const
{
    core::Size size=core->projection->GetTileMatrixMaxXY(MinZoom());
    core::Size tilesize=core->projection->TileSize();
    QSize rsize((size.Width()+1)*tilesize.Width(),(size.Height()+1)*tilesize.Height());
    return rsize;
}

} // end of namespace mapcontrol
